#include <sys/select.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>

#include <errno.h>


#define MAX_N 32

#define TIMEOUT  0
#define BUF_SIZE 1024


int handler(int fd_in, int fd_out, int idx)
{
	int ret = 1;
	char buf[ BUF_SIZE+1];
	fd_set read_set,write_set;
	struct timeval time;
	
	time.tv_sec = TIMEOUT;
	time.tv_usec = 0;
	
	FD_ZERO( &read_set);
	FD_SET ( fd_in, &read_set);

	int max = (fd_in > fd_out) ? fd_in : fd_out;
	
	select( max+1, &read_set, NULL, NULL, NULL);
	int read_count = read( fd_in, buf, BUF_SIZE);
	if ( read_count == -1)
	{
		if ( errno == EINTR)
			return 1;
		else
		{
			perror("Child read error");
			exit(0);
		}
		
	}
	if ( read_count == 0)
	{
		ret = 0;
		buf[ 0] = '\0';
		read_count = 1;
		
		if ( idx == 3)
			sleep(5);
	}
	
	if ( idx == 3)
		sleep(5);
	
	if ( (read_count > 0) && ( buf[ read_count-1] == '\0'))
	{
		ret = 0;
	}
	
	int write_count = 0;
	do
	{
		FD_ZERO( &write_set);
		FD_SET ( fd_out,&write_set);
	
		select( max+1, NULL, &write_set, NULL, NULL);
		int tmp = write( fd_out, &buf[  write_count], read_count - write_count);
		
		if ( tmp == -1)
		{
			if ( errno == EINTR)
				continue;
			else
			{
				perror("Child write error");
				exit(0);
			}	
		}
		write_count += tmp;
	}
	while( read_count != write_count);

	return ret;
}

int max_fd( int **fd_pipes, int N)
{
	int i;
	int max = -1;
	for ( i = 0; i < 2*N; i++)
	{
		if ( max < fd_pipes[i][0])
		{
			max = fd_pipes[i][0];
		}
		if ( max < fd_pipes[i][1])
		{
			max = fd_pipes[i][1];
		}
	}
	return max;
}





int main( int argc, char **argv)
{
	if ( argc != 3)
	{
		printf("Wrong arguments\n");
		exit(1);
	}
	
	int fd_in = open( (char*)argv[2],O_RDONLY , 0644 | O_NONBLOCK);
	if ( fd_in == -1)
	{
		printf("Can't open file: %s\n",(char*)argv[2]);
		exit(1);
	}
	int N = 1;
	if ( !sscanf( (char*)argv[1],"%d",&N) || N <= 0 )
	{
		printf("Wrong number\n");
		exit(1);
	}
	if ( N > MAX_N)
	{
		printf("N is too large %d\n",N);
		exit(1);
	}
//////////////////////////////////////////////////////////
	int  **fd_pipes = malloc( (2*N)*sizeof( int** ));
	int tmp;
	int i;
	for ( i = 0; i <= (2*N-1); i++)
	{
		fd_pipes[ i] = malloc( 2*sizeof(int));
		pipe2( fd_pipes[ i], O_NONBLOCK);
	}
	int fd_out = open( "out_file",O_WRONLY |O_NONBLOCK| O_CREAT,0644);
	
	close ( fd_pipes[ 2*N-1][ 1]);
	fd_pipes[ 2*N-1][ 1] = fd_out;
	
	int max = max_fd( fd_pipes,N);
	//printf( "\tMax = %d",max);
///////////////////////////////////////////////////////////
	for ( i = 0; i < N; i++)
	{
		int tmp = fork();
		if ( tmp == 0)
		{
			//sleep(1);
			if ( i == 0)
			{
				while( handler( fd_in, fd_pipes[0][1], i))
				{}
			}else
			{
				while( handler( fd_pipes[ 2*i - 1][0], fd_pipes[ 2*i][1], i))
				{}
			
				
			}
			
			for ( i = 0; i <= 2*N-1; i++)
			{
				free( fd_pipes[ i]);
				fd_pipes[ i] = NULL;
			}
			free( fd_pipes);
			fd_pipes = NULL;
			exit(1);
		}
	}
// ///////////////////////////////////////////////////////////
	tmp = 1;
	char **buffers       = malloc( N*sizeof( char**)+1);
	int   *buffers_size  = malloc( N*sizeof( int)  );
	int   *buffers_read_count  = malloc ( N*sizeof( int));
	int   *buffers_write_count = malloc ( N*sizeof( int));
	for ( i = 0; i < N; i++)
	{
		buffers_read_count[ i] = 0;
		buffers_write_count[ i] =0;
		if ( i > 5)
		{
			int size = 100*1024;
			buffers[ i]      = malloc( size);
			buffers_size[ i] = size;
		}else
		{
			int size = tmp * 1024;
			buffers     [ i] = malloc( tmp*1024);
			buffers_size[ i] = size;
		}
		
		//close( fd_pipes[ 2*i]  [ 1]);
		//close( fd_pipes[ 2*i+1][ 0]);
		
		tmp *=3;
	}
///////////////////////////////////////////////////////////
	fd_set read_fd;
	fd_set write_fd;
	struct timeval time;

	int T = 1;
	while ( T)
	{
		time.tv_sec = TIMEOUT;
		time.tv_usec = 0;
		
		FD_ZERO( &read_fd);
		FD_ZERO( &write_fd);
		for ( i = 0; i < N; i++)
		{
			if ( buffers_read_count[ i] == 0)
			{
				FD_SET  ( fd_pipes[ 2*i][0], &read_fd );
			}
			else
			{
				FD_SET  ( fd_pipes[ 2*i+1][1], &write_fd);
			}
		}
		
		select( max+1, &read_fd, &write_fd, NULL, NULL);
//		sleep(1);
		
		for ( i = 0; i < N; i++)
		{
			if ( FD_ISSET( fd_pipes[ 2*i][0], &read_fd))
			{	
				int read_count = read( fd_	close( fd_out);
				pipes[ 2*i][0], buffers[ i], buffers_size[ i]);
				if ( read_count == -1)
				{
					if ( errno == EINTR)
						continue;
					else
					{
						perror("Parent read error");
						exit(0);
					}
				}
				buffers_read_count[ i] += read_count;
				if ( ( i == N-1) && ( buffers[ i][ buffers_read_count[ i] - 1] == '\0') )
				{
					T = 0;
				}
			}
			if ( !T || FD_ISSET( fd_pipes[ 2*i+1][1], &write_fd))
			{
				if ( T != 0)
				{
					int write_count = 
							write( 
								fd_pipes[ 2*i+1][1], 
								&buffers[ i][buffers_write_count[ i]], 
								buffers_read_count[ i] - buffers_write_count[ i]
							 );
					if ( write_count == -1)
					{
						if ( errno == EINTR)
							continue;
						else
						{
							perror("Parent write");
							exit(0);
						}
					}
							
					buffers_write_count[ i] += write_count;
					if ( buffers_read_count[ i] == buffers_write_count[ i])
					{
						buffers_read_count[ i] = 0;
						buffers_write_count[ i]= 0;
					}
				}
				else
				{
					int write_count = 
						write( 
							fd_pipes[ 2*i+1][1], 
							&buffers[ i][buffers_write_count[ i]], 
							buffers_read_count[ i] - buffers_write_count[ i] -1
							);
					if ( write_count == -1)
					{
						if ( errno == EINTR)
							continue;
						else
						{
							perror("Parent write");
							exit(0);
						}
					}
					
					buffers_write_count[ i] += write_count;
					if ( ( buffers_read_count[ i] - 1) == buffers_write_count[ i])
					{
						buffers_read_count[ i] = 0;
					}
				}
			}
		}
		
	}
/////////////////////////////////////////////////////////
	for ( i = 0; i < N; i++)
	{
		free( buffers[ i]);
		free( fd_pipes[ 2*i]);
		free( fd_pipes[ 2*i+1]);
		
	}
	free( fd_pipes);
	free( buffers);
	free( buffers_size);
	free( buffers_read_count);
	free( buffers_write_count);
	close( fd_in);
	close( fd_out);
	
	exit(0);
}